/*
 * @Descripttion:  二次封装axios
 * @version:
 * @Author: lhl
 * @Date: 2024-01-10 11:09:57
 * @LastEditors: lhl
 * @LastEditTime: 2024-01-18 09:43:54
 */
import { createVNode } from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { message, Modal } from 'ant-design-vue'
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
// 引入qs（内置）模块如果没有报错请下载，用来序列化post类型的数据
import QS from 'qs'
import router from '../router'

import { useSeverLoadingStore } from '~store/requestLoading'
const store = useSeverLoadingStore() // 调用方法,控制加载动画的开启关闭 store.isLoading(true) store.isLoading(false)
console.log(store, 'store')

declare module 'axios' {
  interface AxiosResponse<T = any> {
    // 这个地方为AxiosResponse添加扩展属性
    code: number
    result: string
    message: string | undefined | null
    data: T
  }
}

/**
 * 跳转登录页
 * 携带当前页面路由，以期在登录页面完成登录后返回当前页面
 */
const toLogin = () => {
  router.replace({
    path: '/login',
    query: {
      redirect: router.currentRoute.value.fullPath
    }
  })
}

/**
 * @desc 删除缓存队列中的请求
 * @param {String} reqKey 本次请求的唯一标识 url&method
 */
function removeCacheRequest(reqKey) {
  if (cacheRequest[reqKey]) {
    // 通过AbortController实例上的abort来进行请求的取消
    cacheRequest[reqKey].abort()
    delete cacheRequest[reqKey]
  }
}

// 注意：AbortController是fetch API提供的，不需要从axios引入
// isCancel用于判断请求是不是被AbortController取消的
const { isCancel } = axios
// 请求队列，缓存发出的请求
const cacheRequest = {}
// 不进行重复请求拦截的白名单  接口路径 + 请求方式放入白名单
const cacheWhiteList = ['/agent/agentSearch/list1&get']

console.log(import.meta.env.VITE_BASE_URL, '域名前缀')

// 环境的切换 方便本地测试
const isProd = process.env.NODE_ENV === 'production'
let baseURL = ''
// 方便联调
isProd ? (baseURL = import.meta.env.VITE_BASE_URL) : (baseURL = import.meta.env.VITE_BASE_URL)

// 创建 axios 实例
const service: AxiosInstance = axios.create({
  baseURL: baseURL,
  timeout: 10000 // 请求超时时间
  // axios默认是请求的时候不会带上cookie的，需要通过设置 withCredentials:true来解决
  // 当配置了xhr.withCredentials=true 时，必须在后端增加 response 头信息Access-Control-Allow-Origin，且必须指定域名，而不能指定为*
  // 如果在同域下配置xhr.withCredentials，无论配置true还是false，效果都会相同，且会一直提供凭据信息(cookie、HTTP认证及客户端SSL证明等)
  // withCredentials: true, // 可要可不要  跨域请求时发送Cookie 如果用的JSONP，可以配置此参数带上cookie凭证，如果是代理和CORS不用设置
  // headers:{'Content-Type': 'application/json; charset=UTF-8'} // 这里可以给个默认json格式的头
})

// 设置请求超时
// service.defaults.timeout = 10000

// post请求头的设置
// post请求的时候，我们需要加上一个请求头，所以可以在这里进行一个默认的设置，即设置post的请求头为 application/x-www-form-urlencoded;charset=UTF-8
service.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'

// 注意：从 v0.22.0 开始，Axios 支持以 fetch API 方式—— AbortController 取消请求，CancelToken API被弃用，使用过程中能用 AbortController 就尽量别用 CancelToken

// 封装请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // 在请求发送之前做一些处理
    // if (config.isLoading || (!config.isLoading && config.isLoading != false)) {
    //   // isLoading 有的请求需要隐藏loading
    //   showLoading(config.loadingText)
    // }

    const { url, method } = config
    // 请求地址和请求方式组成唯一标识，将这个标识作为取消函数的key，保存到请求队列中
    const reqKey = `${url}&${method}`
    // 如果存在重复请求，删除之前的请求
    if (cacheWhiteList.indexOf(reqKey) === -1) {
      removeCacheRequest(reqKey)
      // 将请求加入请求队列，通过AbortController来进行手动取消
      const controller = new AbortController()
      config.signal = controller.signal
      cacheRequest[reqKey] = controller
    }

    // 发送请求之前做某件事
    if (config.method === 'post' || config.method === 'put') {
      config.data = QS.stringify(config.data)
    }

    // 拼上token 缓存直接拿 || store 里面取
    // token 一般是在登录完成之后，将用户的token通过localStorage或者cookie存在本地，然后用户每次在进入页面的时候（即在main.js中），
    // 会首先从本地存储中读取token，如果token存在说明用户已经登陆过，则更新vuex中的token状态。然后，在每次请求接口的时候，都会在请求的header中携带token，
    // 后台人员就可以根据你携带的token来判断你的登录是否过期，如果没有携带，则说明没有登录过，要是一个页面不需要用户登录就可以访问的 前端的请求可以携带token，但是后台可以选择不接收！
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['X-Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
    }
    return config
  },
  (error: AxiosError) => {
    // hideLoading()
    // 对请求错误做些什么
    console.log(error, 'error')
    return Promise.reject(error)
  }
)

// 封装响应拦截器
service.interceptors.response.use(
  (response: AxiosResponse) => {
    console.log(response, 'response')
    // hideLoading()
    // 对响应数据做些什么
    // 请求成功，从队列中移除
    const { url, method, responseType } = response.config
    removeCacheRequest(`${url}&${method}`)

    // status 返回 200 之后的接口响应处理
    const { data } = response

    // 二进制数据则直接返回
    if (responseType === 'blob' || responseType === 'arraybuffer') {
      return data
    }

    if (data.code === 200 || data.code === 0 || data.success) {
      // 接口请求成功
      return data
    } else {
      message.error(data.message || '啊哦，服务开小差了~')
    }
  },
  (error: AxiosError) => {
    // hideLoading()
    // 对响应错误做些什么
    // 请求失败，使用isCancel来区分是被CancelToken取消，还是常规的请求失败
    if (isCancel(error)) {
      // 通过CancelToken取消的请求不做任何处理
      return Promise.reject({
        message: '重复请求，自动拦截并取消'
      })
    } else {
      // 正常请求发生错误,抛出异常等统一提示
      const { response } = error as any
      if (response) {
        // 请求已发出，但是不在2xx的范围
        errorHandle(response.status, response.data.message)
        return Promise.reject(response)
      } else {
        // 处理断网的情况
        // eg:请求超时或断网时，更新state的network状态
        // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
        // 关于断网组件中的刷新重新获取数据处理
        if (!window.navigator.onLine) {
          // store.commit('changeNetwork', false)
        } else {
          return Promise.reject(error)
        }
      }
    }
    return Promise.reject(error)
  }
)

// 错误回调函数 统一处理错误信息 该弹提示弹提示 该弹窗弹窗 该跳转跳转
const errorHandle = (status, msg) => {
  // 状态码判断
  switch (status) {
    // 401: 未登录状态，跳转登录页
    case 401:
      toLogin()
      break
    // 403 token过期
    // 清除token并跳转登录页
    case 403:
      // localStorage.removeItem('token');
      Modal.confirm({
        title: '系统提示',
        icon: createVNode(ExclamationCircleOutlined),
        content: '登录状态已过期，您可以继续留在该页面，或者重新登录',
        okText: '重新登录',
        cancelText: '取消',
        // okType: 'danger',
        onOk: () => {
          console.log('onOk')
          // 方式一 用 store 清除token以及登录相关数据
          // store.dispatch('LogOut').then(() => {
          //   location.href = '/index';
          // })
          // 方式二 手动清除数据回到登录界面
          // setTimeout(() => {
          //   toLogin()
          // }, 1000)
        },
        onCancel() {
          console.log('Cancel')
        }
      })
      break
    // 404请求不存在
    case 404:
      message.error(msg || '请求的资源不存在')
      break
    default:
      message.error(msg || '未知错误')
      console.log(msg, 'msg')
  }
}

export default service
